module share.circularbuffer;

const DEFAULT_SIZE = 1024 * 4;


public struct CircularBuffer(T)
{
    private int _capacity;
    private T[] _buffer;

    private int _head_pos;
    private int _tail_pos;

    public this(int capacity = DEFAULT_SIZE)
    {
        assert (capacity > 0);

        _capacity = capacity;
        _buffer = new T[capacity];

        _head_pos = 0;
        _tail_pos = 0;
    }

    public ~this()
    {
        if (_capacity > 0)
        {
            assert(_buffer !is null);
            destroy(_buffer);
        }
    }

    public void PutData(in T data)
    {
        immutable len = 1;
        while (IsOverFlowCondition(len))
            BufferResize();
        _buffer[_tail_pos++] = data;
        if (_tail_pos == _capacity)
            _tail_pos = 0;
    }

    public void PutData(in T[] data, int len)
    {
        if (len <= 0)
            return;

        while (IsOverFlowCondition(len))
            BufferResize();

        if (IsIndexOverFlow(len))
        {
            immutable FirstCopyLen = _capacity - _tail_pos;
            immutable SecondCopyLen = len - FirstCopyLen;
            assert(FirstCopyLen > 0);
            _buffer[_tail_pos .. _tail_pos + FirstCopyLen] = data[0 .. FirstCopyLen];
            //Move(pData^, (FBuffer+FTailPos)^, FirstCopyLen);
            if (SecondCopyLen > 0)
            {
                _buffer[0 .. SecondCopyLen] = data[FirstCopyLen .. FirstCopyLen + SecondCopyLen];
                //Move((pData+FirstCopyLen)^, FBuffer^, SecondCopyLen);
                _tail_pos = SecondCopyLen;
            }
            else
                _tail_pos = 0;
        }
        else
        {
            _buffer[_tail_pos .. _tail_pos + len] = data[0 .. len];
            //Move(pData^, (FBuffer+FTailPos)^, len);
            _tail_pos += len;
        }
    }

    public void GetData(ref T[] data, int len)
    {
        assert((len > 0) && (len <= GetValidCount()));
        if (len < _capacity - _head_pos)
            data[0 .. len] = _buffer[_head_pos .. _head_pos + len];
            //Move((FBuffer+FHeadPos)^, pData^, len);
        else
        {
            immutable fc = _capacity - _head_pos;
            immutable sc = len - fc;
            data[0 .. fc] = _buffer[_head_pos .. _head_pos + fc];
            //Move((FBuffer+FHeadPos)^, pData^, fc);
            if (sc > 0)
                data[fc .. fc + sc] = _buffer[0 .. sc];
                //Move(FBuffer^, (pData+fc)^, sc);
        }
    }

    public int GetOutData(ref T[] data)
    {
        immutable len = GetValidCount();
        immutable fc = _capacity - _head_pos;
        if (len > fc)
        {
            immutable sc = len - fc;
            data[0 .. fc] = _buffer[_head_pos .. _head_pos + fc];
            data[fc .. fc + sc] = _buffer[0 .. sc];
            //Move((FBuffer+FHeadPos)^, pData^, fc);
            //Move(FBuffer^, (pData+fc)^, sc);
            _head_pos = sc;
            assert(_head_pos == _tail_pos);
        }
        else
        {
            data[0 .. len] = _buffer[_head_pos .. _head_pos + len];
            //Move((FBuffer+FHeadPos)^, pData^, len);
            _head_pos += len;
            if (_head_pos == _capacity)
                _head_pos = 0;
        }
        return len;
    }

    public T GetHeadData()
    {
        return _buffer[_head_pos];
    }

    public bool HeadIncrease(int increasement = 1)
    {
        assert(increasement <= GetValidCount);
        _head_pos += increasement;
        _head_pos = _head_pos % _capacity;
        return (_head_pos != _tail_pos);
    }

    public void SetEmpty()
    {
        _head_pos = 0;
        _tail_pos = 0;
    }

    public int GetValidCount()
    {
        auto count = _tail_pos - _head_pos;
        if (count < 0) count = _capacity + count;
        return count;
    }

    protected bool IsOverFlowCondition(int len)
    {
        return (len >= _capacity - GetValidCount());
    }

    protected bool IsIndexOverFlow(int len)
    {
        return (len + _tail_pos >= _capacity);
    }

    protected void BufferResize()
    {
        T[] pNewData;
        
        assert(_capacity  > 0);
        immutable prevsize = _capacity;
        _capacity = _capacity << 1; // double capacity
        pNewData.length = _capacity;
        pNewData[0 .. prevsize] = _buffer[0 .. prevsize];
        //GetMem(pNewData, FBufferSize);
        //Move(FBuffer^, pNewData^, prevsize);
        if (_tail_pos < _head_pos)
        {
            pNewData[prevsize .. prevsize + _tail_pos] = _buffer[0 .. _tail_pos];
            //Move(FBuffer^, (pNewData+prevsize)^, FTailPos);
            _tail_pos += prevsize;
        }
        _buffer = null;
        //FreeMem(FBuffer);
        _buffer = pNewData;
    }

    @property const nothrow public int capacity() { return _capacity; }
    @property const nothrow public int head() { return _head_pos; }
    @property const nothrow public int tail() { return _tail_pos; }


    unittest
    {
        alias CircularBuffer!ubyte ByteCircularBuffer;

        ByteCircularBuffer buffer = ByteCircularBuffer(1);

        assert(buffer.capacity() == 1, "CircularBuffer size wrong");
        buffer.PutData(50);
        assert(buffer.capacity() == 2, "CircularBuffer size wrong");
        buffer.PutData(100);
        assert(buffer.capacity() == 4, "CircularBuffer size wrong");

        ubyte data[16];
        buffer.GetData(data[], 1);
        assert(data[0] == 50, "CircularBuffer value wrong");

        buffer.HeadIncrease(1);
        ubyte val = buffer.GetHeadData();
        assert(val == 100, "CircularBuffer value wrong");

        assert(buffer.capacity() == 4, "CircularBuffer size wrong");

        buffer.PutData(80);
        buffer.SetEmpty();
        assert(buffer.GetValidCount() == 0);

        buffer.PutData([1, 50, 3], 3);
        buffer.GetOutData(data[]);
        assert(data[0 .. 3] == [1, 50, 3], "CircularBuffer values wrong");
    }
}